package net.lab1024.sa.admin.module.business.aps.domain.optaplanner;

import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintCollectors;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaplanner.core.api.score.stream.Joiners;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

/**
 * OptaPlanner约束提供者
 * 定义APS排程优化的约束规则
 *
 * @Author 1024创新实验室
 * @Date 2025-01-28
 * @Wechat zhuoda1024
 * @Email lab1024@163.com
 * @Copyright <a href="https://1024lab.net">1024创新实验室</a>
 */
public class ScheduleConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[]{
                // 硬约束
                resourceConflict(constraintFactory),
                timeSlotConflict(constraintFactory),
                resourceTypeMatch(constraintFactory),
                timeConstraints(constraintFactory),
                resourceCapacity(constraintFactory),
                predecessorConstraint(constraintFactory),
                
                // 软约束
                minimizeMakespan(constraintFactory),
                prioritizeHighPriorityTasks(constraintFactory),
                minimizeOvertimeCost(constraintFactory),
                balanceResourceLoad(constraintFactory),
                minimizeSetupTime(constraintFactory)
        };
    }

    /**
     * 硬约束：资源冲突
     * 同一资源不能同时处理多个任务
     */
    private Constraint resourceConflict(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .join(Task.class,
                        Joiners.equal(Task::getAssignedResource),
                        Joiners.lessThan(Task::getTaskId))
                .filter((task1, task2) -> {
                    if (task1.getStartTimeSlot() == null || task2.getStartTimeSlot() == null) {
                        return false;
                    }
                    LocalDateTime task1End = task1.getStartTimeSlot().getStartTime().plusMinutes(task1.getDuration());
                    LocalDateTime task2Start = task2.getStartTimeSlot().getStartTime();
                    LocalDateTime task2End = task2.getStartTimeSlot().getStartTime().plusMinutes(task2.getDuration());
                    LocalDateTime task1Start = task1.getStartTimeSlot().getStartTime();
                    
                    // 检查时间重叠
                    return !(task1End.isBefore(task2Start) || task1Start.isAfter(task2End));
                })
                .penalize(HardSoftScore.ONE_HARD)
                .asConstraint("资源冲突");
    }

    /**
     * 硬约束：时间槽冲突
     * 同一时间槽不能被多个任务使用
     */
    private Constraint timeSlotConflict(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .join(Task.class,
                        Joiners.equal(Task::getStartTimeSlot),
                        Joiners.lessThan(Task::getTaskId))
                .filter((task1, task2) -> task1.getStartTimeSlot() != null)
                .penalize(HardSoftScore.ONE_HARD)
                .asConstraint("时间槽冲突");
    }

    /**
     * 硬约束：资源类型匹配
     * 任务必须分配给匹配类型的资源
     */
    private Constraint resourceTypeMatch(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .filter(task -> !task.isResourceTypeMatched())
                .penalize(HardSoftScore.ONE_HARD)
                .asConstraint("资源类型不匹配");
    }

    /**
     * 硬约束：时间约束
     * 任务必须在允许的时间范围内执行
     */
    private Constraint timeConstraints(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .filter(Task::isTimeConstraintViolated)
                .penalize(HardSoftScore.ONE_HARD)
                .asConstraint("时间约束违反");
    }

    /**
     * 硬约束：资源容量
     * 资源的并发任务数不能超过其容量
     */
    private Constraint resourceCapacity(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .filter(task -> task.getAssignedResource() != null && task.getStartTimeSlot() != null)
                .groupBy(Task::getAssignedResource, 
                        task -> task.getStartTimeSlot().getStartTime().toLocalDate(),
                        ConstraintCollectors.count())
                .filter((resource, date, count) -> count > resource.getCapacity())
                .penalize(HardSoftScore.ONE_HARD, (resource, date, count) -> count - resource.getCapacity())
                .asConstraint("资源容量超限");
    }

    /**
     * 硬约束：前置任务约束
     * 任务必须在其前置任务完成后才能开始
     */
    private Constraint predecessorConstraint(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .join(Task.class)
                .filter((task, predecessor) -> {
                    if (task.getPredecessorTaskIds() == null || task.getStartTimeSlot() == null || predecessor.getStartTimeSlot() == null) {
                        return false;
                    }
                    
                    List<String> predecessorIds = Arrays.asList(task.getPredecessorTaskIds().split(","));
                    if (!predecessorIds.contains(predecessor.getTaskId().toString())) {
                        return false;
                    }
                    
                    LocalDateTime predecessorEnd = predecessor.getEndTime();
                    LocalDateTime taskStart = task.getStartTimeSlot().getStartTime();
                    
                    return taskStart.isBefore(predecessorEnd);
                })
                .penalize(HardSoftScore.ONE_HARD)
                .asConstraint("前置任务约束违反");
    }

    /**
     * 软约束：最小化完工时间
     * 尽量减少整体项目的完工时间
     */
    private Constraint minimizeMakespan(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .filter(task -> task.getEndTime() != null)
                .penalize(HardSoftScore.ONE_SOFT, 
                        task -> (int) task.getEndTime().toLocalDate().toEpochDay())
                .asConstraint("最小化完工时间");
    }

    /**
     * 软约束：优先处理高优先级任务
     * 高优先级任务应该更早开始
     */
    private Constraint prioritizeHighPriorityTasks(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .filter(task -> task.getStartTimeSlot() != null && task.getPriority() != null)
                .reward(HardSoftScore.ONE_SOFT, 
                        task -> task.getPriority() * 100 - (int) task.getStartTimeSlot().getStartTime().toLocalDate().toEpochDay())
                .asConstraint("优先处理高优先级任务");
    }

    /**
     * 软约束：最小化加班成本
     * 尽量避免在加班时间安排任务
     */
    private Constraint minimizeOvertimeCost(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .filter(task -> task.getStartTimeSlot() != null && 
                               task.getStartTimeSlot().getCostFactor() != null &&
                               task.getStartTimeSlot().getCostFactor() > 1.0)
                .penalize(HardSoftScore.ONE_SOFT, 
                        task -> (int) ((task.getStartTimeSlot().getCostFactor() - 1.0) * 100))
                .asConstraint("最小化加班成本");
    }

    /**
     * 软约束：平衡资源负载
     * 尽量平均分配任务到各个资源
     */
    private Constraint balanceResourceLoad(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .filter(task -> task.getAssignedResource() != null)
                .groupBy(Task::getAssignedResource, ConstraintCollectors.count())
                .penalize(HardSoftScore.ONE_SOFT, (resource, count) -> count * count)
                .asConstraint("平衡资源负载");
    }

    /**
     * 软约束：最小化设置时间
     * 相同类型的任务连续执行可以减少设置时间
     */
    private Constraint minimizeSetupTime(ConstraintFactory constraintFactory) {
        return constraintFactory
                .forEach(Task.class)
                .join(Task.class,
                        Joiners.equal(Task::getAssignedResource),
                        Joiners.lessThan(task -> task.getStartTimeSlot() != null ? 
                                task.getStartTimeSlot().getStartTime() : LocalDateTime.MIN))
                .filter((task1, task2) -> {
                    if (task1.getRequiredResourceType() == null || task2.getRequiredResourceType() == null) {
                        return false;
                    }
                    return !task1.getRequiredResourceType().equals(task2.getRequiredResourceType());
                })
                .penalize(HardSoftScore.ONE_SOFT, (task1, task2) -> 10)
                .asConstraint("最小化设置时间");
    }
}